
package clientv3

import (
	"context"
	"sync"

	pb "go.etcd.io/etcd/api/v3/etcdserverpb"

	"google.golang.org/grpc"
)

// Txn是封装小型事务的接口。
// 
// Txn（context.TODO（））。如果（
// 比较（值（k1），“>”，v1），
// 比较（版本（k1），“=”，2）
// ）。然后（
// OpPut（k2，v2），OpPut（k3，v3）
// ）。其他（
// OpPut（k4，v4），OpPut（k5，v5）
// ）。Commit（）
// 
type Txn interface {
	// 添加 Compare 条件
	If(cs ...Cmp) Txn

	// 如果 Compare 条件全部成立，则执行 Then （）方法中添加的全部操作
	Then(ops ...Op) Txn

	// 如果 Compare 条件不成立，则执行 Else （）方法中添加的全部操作
	Else(ops ...Op) Txn

	// 提交 Txn
	Commit() (*TxnResponse, error)
}

type txn struct {
	// 关联的 kv 实例 。
	kv  *kv
	ctx context.Context

	mu    sync.Mutex
	// 是否设置 Compare 条件，设置一次则 该字段就会被设置为 true 。
	cif   bool
	// 是否向上面的 SUS 集合中添加了操作。
	cthen bool
	// 是否向上面的 fas 集合中添加操作。
	celse bool
	// 如果当前 txn 实例中记录的操作全部是读操作，则该字段为 false,否则为 true 。
	isWrite bool
	// 用于记录 Compare 条件的集合
	cmps []*pb.Compare
	// 全部 Compare 条件通过之后执行的操作列表。
	sus []*pb.RequestOp
	// 任一 Compare 条件检测失败之后执行的操作列表。
	fas []*pb.RequestOp

	callOpts []grpc.CallOption
}

func (txn *txn) If(cs ...Cmp) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

	if txn.cif {
		panic("cannot call If twice!")
	}

	if txn.cthen {
		panic("cannot call If after Then!")
	}

	if txn.celse {
		panic("cannot call If after Else!")
	}

	txn.cif = true

	for i := range cs {
		txn.cmps = append(txn.cmps, (*pb.Compare)(&cs[i]))
	}

	return txn
}

func (txn *txn) Then(ops ...Op) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

	if txn.cthen {
		panic("cannot call Then twice!")
	}
	if txn.celse {
		panic("cannot call Then after Else!")
	}

	txn.cthen = true

	for _, op := range ops {
		txn.isWrite = txn.isWrite || op.isWrite()
		txn.sus = append(txn.sus, op.toRequestOp())
	}

	return txn
}

func (txn *txn) Else(ops ...Op) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

	if txn.celse {
		panic("cannot call Else twice!")
	}

	txn.celse = true

	for _, op := range ops {
		txn.isWrite = txn.isWrite || op.isWrite()
		txn.fas = append(txn.fas, op.toRequestOp())
	}

	return txn
}

// txn.Commit（）方法，该方法最终会调用 KVClient.Txn（） 方法进行处理
func (txn *txn) Commit() (*TxnResponse, error) {
	txn.mu.Lock()
	defer txn.mu.Unlock()
	// 创建 TxnRequest 实例
	r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas}

	var resp *pb.TxnResponse
	var err error
	// 调用 KVClient.Txn （）方法与服务均交互
	resp, err = txn.kv.remote.Txn(txn.ctx, r, txn.callOpts...)
	if err != nil {
		return nil, toErr(txn.ctx, err)
	}
	return (*TxnResponse)(resp), nil
}
